package earlyDetection;

import config.Config;
import earlyDetection.pg.PGEdge;
import network.Device;
import network.Network;
import networkLoader.FBNetwork;
import org.jgrapht.Graph;
import org.jgrapht.GraphPath;
import org.jgrapht.alg.connectivity.ConnectivityInspector;
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import verifier.Ports.Ports;
import verifier.util.BDDEngine;

import java.util.*;

public class PolicyCheckerNaive {
    private HashMap<Integer, Graph<Device, PGEdge>> ecToGraph = new HashMap<>();
    private Graph<Device, PGEdge> graph;
    private int hs;
    private Set<Device> closed = new HashSet<>();
    private BDDEngine bddEngine;
    private Network network;
    public boolean stop = false;
    private boolean foundValid = false;

    public PolicyCheckerNaive(Network network, Graph<Device, PGEdge> graph, BDDEngine bddEngine, int hs) {
        this.network = network;
        this.graph = graph;
        this.bddEngine = bddEngine;
        this.hs = hs;
    }

    public void check(Device newClosed, Collection<Integer> ECs, HashMap<Integer, Ports> ecToPorts, Automata automata) {
        this.closed.add(newClosed);
        foundValid = false;
        automata.setCurrentState(0);
        for (int ec : ECs) {
            if (bddEngine.and(ec, hs) != 0) {
                if (!this.ecToGraph.containsKey(ec)) {
                    // create pg copy
                    Graph<Device, PGEdge> graph = cloneGraph();
                    // remove edges for closed switches
                    reducePG(graph, ecToPorts, ec);
                    this.ecToGraph.put(ec, graph);
                }
                Graph<Device, PGEdge> pg = ecToGraph.get(ec);

                Set<PGEdge> needToRemove = new HashSet<>();
                for (PGEdge edge : pg.outgoingEdgesOf(newClosed)) {
                    if (!ecToPorts.get(ec).getAll().contains(edge.port)) {
                        needToRemove.add(edge);
                    }
                }
                for (PGEdge edge : needToRemove) {
                    pg.removeEdge(edge);
                }

                System.out.println("#closed: " + closed.size());
                long s = System.nanoTime();
                automata.setCurrentState(0);
                traverse(network.getDevice("rsw-0-0"), pg, automata);
                System.out.println("$compute connected: " + (System.nanoTime() - s));
                if (!foundValid) {
                    stop = true;
                    System.out.println("naive cannot reach!");
                    System.out.println("naive #closed: " + closed.size());
                    System.out.println("#processed updates: " + Config.processedUpdates);
                    System.out.println("naive $time: " + (System.nanoTime() - Config.startAt));
                }

            }
        }
    }

    private Graph<Device, PGEdge> cloneGraph() {
        Graph<Device, PGEdge> graph = new DefaultDirectedGraph<>(PGEdge.class);
        for (Device device : this.graph.vertexSet()) {
            graph.addVertex(device);
        }
        for (PGEdge edge : this.graph.edgeSet()) {
            Device src = this.graph.getEdgeSource(edge);
            Device dst = this.graph.getEdgeTarget(edge);
            graph.addEdge(src, dst, new PGEdge(src.getPort(src.getName() + ">" + dst.getName())));
        }
        return graph;
    }

    private void reducePG(Graph<Device, PGEdge> pg, HashMap<Integer, Ports> ecToPorts, int ec) {
        for (Device device : pg.vertexSet()) {
            if (!closed.contains(device)) {
                continue;
            }

            Set<PGEdge> needToRemove = new HashSet<>();
            for (PGEdge edge : pg.outgoingEdgesOf(device)) {
                if (!ecToPorts.get(ec).getAll().contains(edge.port)) {
                    needToRemove.add(edge);
                }
            }
            for (PGEdge edge : needToRemove) {
                pg.removeEdge(edge);
            }
        }
        System.out.println(pg.edgeSet().size());
    }

    private void traverse(Device current, Graph<Device, PGEdge> pg, Automata automata) {
        if (foundValid) return;
        int state = automata.getCurrentState();
        automata.take(current.getName());
        if (automata.isAccept()) {
            foundValid = true;
            return;
        }
        if (automata.getCurrentState() == -1) {
            automata.setCurrentState(state);
            return;
        }
        for (PGEdge edge : pg.outgoingEdgesOf(current)) {
            if (foundValid) return;
            traverse(edge.port.getPeerDevice(), pg, automata);
        }
        automata.setCurrentState(state);
    }
}